﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Microsoft.Xna.Framework;
using Barebones.Structure;
using Barebones.Components;
using Barebones.Dependencies;
using Barebones.Communication;
using Barebones.Communication.Messages;

namespace Barebones.Framework
{
    public class Entity : IDisposable
    {
        bool m_Dirty = true;
        Engine m_Engine;
        Matrix m_World = Matrix.Identity;
        Matrix m_Transform = Matrix.Identity;
        LogicContainer m_Components = new LogicContainer();
        Forum m_Forum = new Forum();

        List<Entity> m_Children = new List<Entity>();

        public String Name
        { get; set; }

        public Engine Engine
        { get { return m_Engine; } }

        public Forum Forum
        { get { return m_Forum; } }

        public Entity Parent
        { get; internal set; }

        public Matrix Transform
        {
            get { return m_Transform; }
            set
            {
                m_Transform = value;
                SetDirty();
            }
        }

        internal Entity(Engine owner)
        {
            m_Engine = owner;
        }

        public Matrix GetWorld()
        {
            GuardWorldMatrix();

            return m_World;
        }

        public Vector3 GetWorldTranslation()
        {
            GuardWorldMatrix();

            return m_World.Translation;
        }

        /// <summary>
        /// no matter the hierarchy, places an object in the world with the desired transform
        /// </summary>
        /// <param name="transform"></param>
        public void PlaceInWorld(Matrix desired)
        {
            Transform = Matrix.Identity;
            Transform = desired * Matrix.Invert(GetWorld());
        }

        public void PlaceInWorld(Vector3 desired)
        {
            m_Transform.Translation = Vector3.Zero;
            SetDirty();

            Vector3 worldtrans = GetWorldTranslation();
            m_Transform.Translation = desired - worldtrans;
            SetDirty();
        }

        public void Translate(Vector3 amount)
        {
            m_Transform.Translation += amount;
            SetDirty();
        }

        public void SetTranslation(Vector3 value)
        {
            m_Transform.Translation = value;
            SetDirty();
        }

        public void Rotate(Vector3 axis, float radians)
        {
            Quaternion trans = Quaternion.CreateFromAxisAngle(axis, radians);
            Matrix.Transform(ref m_Transform, ref trans, out m_Transform);
            SetDirty();
        }

        public Entity CreateChild()
        {
            Entity child = new Entity(Engine);
            child.Parent = this;

            //add item at front so that the update order is in the created order (due to reverse iteration)
            m_Children.Insert(0, child);

            return child;
        }

        public void AddComponent(EntityComponent item)
        {
            item.SetOwner(this);
            m_Components.Add(item);

            m_Engine.Forum.Fire<ComponentAddedMessage>( new ComponentAddedMessage() { Component = item } );
        }

        public void RemoveComponent(EntityComponent item)
        {
            m_Engine.Forum.Fire<ComponentRemovedMessage>(new ComponentRemovedMessage() { Component = item });

            m_Components.Remove(item);
            item.SetOwner(null);
        }

        public void Fire<T>(T message) where T : class
        {
            Forum.Fire<T>(message);
        }

        public void FireDown<T>(T message) where T : class
        {
            Forum.Fire<T>(message);

            m_Children.ForReversed(item => item.FireDown<T>(message));
        }

        public void FireUp<T>(T message) where T : class
        {
            Forum.Fire<T>(message);

            if (Parent == null)
                Engine.Forum.Fire<T>(message);
            else
                Parent.FireUp<T>(message);
        }

        public T GetComponent<T>() where T : class, IDependent
        {
            return m_Components.Get<T>();
        }

        public T GetFirstAbstract<T>() where T : class, IDependent
        {
            return m_Components.GetFirstImplementing<T>();
        }

        public IEnumerable<T> GetAbstracts<T>() where T : class, IDependent
        {
            return m_Components.GetAllImplementing<T>();
        }

        public void Update(float dt)
        {
            m_Components.Update(dt);

            m_Children.ForReversed(item => item.Update(dt));
        }

        public void Draw()
        {
            m_Components.Draw();

            m_Children.ForReversed(item => item.Draw());
        }

        public void Dispose()
        {
            if (Parent != null)
                Parent.DisposeChild(this);
            else
                m_Engine.DestroyEntity(this);
        }

        internal void DisposeChild(Entity child)
        {
            if (!m_Children.Remove(child))
                throw new InvalidOperationException("Tried to remove child that isn't present in parent");

            child.Parent = null;
        }

        private void SetDirty()
        {
            m_Dirty = true;
            m_Children.ForEach(child => child.SetDirty());
        }

        private void GuardWorldMatrix()
        {
            if (m_Dirty)
            {
                if (Parent != null)
                    m_World = m_Transform * Parent.GetWorld();
                else
                    m_World = m_Transform;

                m_Dirty = false;
            }
        }
    }
}
